﻿using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using AutoMapper;
using CollegeSim.Domain.DTO;
using CollegeSim.Domain.Interfaces.Repositories;

namespace CollegeSim.Persistence.Repositories
{
    // Author: Ryan Redburn
    // Project CollegeSim
    // Date: 2/5/2014

    #region [ Updates ]

    // Author: Ryan Redburn
    // Date: 2/22/2014
    // Revisions: Introduced exception handling for invalid method arguments

    // Author: Ryan Redburn
    // Date: 2/26/2014
    // Revisions: Added exception handling for unique constraints

    // Author: Ryan Redburn
    // Date: 4/6/2014
    // Revisions: Fixed logic error in update method and added class comments

    // Author: Ryan Redburn
    // Date: 5/6/2014
    // Revisions: Changed repository to reflect version 2.0 changes. FindByStyle() now takes two bool arguments, removed FindByInstructor() and FindByCRN().

    // Author: Ryan Redburn
    // Date: 5/20/2014
    // Revisions: Added exception handling for database connection problems.

    #endregion

    /// <summary>
    /// Repository used to interact with course objects. Provides CRUD and search functionality.
    /// </summary>
    public class CourseRepository : ICourseRepository
    {
        #region [ Methods ]

        /// <summary>
        /// Returns all Courses.
        /// </summary>
        /// <returns>IEnumerable of CourseDto.</returns>
        public IEnumerable<CourseDTO> FindAll()
        {
            IEnumerable<CourseDTO> courses = null;

            // Open entity connnection
            using (var context = new CollegeSimEntities())
            {
                try
                {
                    // Mapping retrieved dbset to targeted return type
                    courses = Mapper.Map<IEnumerable<CourseDTO>>(context.Courses);
                }
                catch (AutoMapperMappingException ex)
                {
                    // Check for database connection exception
                    if (ex.InnerException.InnerException is SqlException)
                    {
                        throw new InvalidOperationException(
                            "A database connection issue has occurred. Check your network connection and try again, or contact the application developer.");
                    }
                }
            }

            return courses;
        }

        /// <summary>
        /// Returns all Courses of the given subject.
        /// </summary>
        /// <param name="subject">Three character Course subject.</param>
        /// <returns>IEnumerable of CourseDto.</returns>
        public IEnumerable<CourseDTO> FindBySubject(string subject)
        {
            // Throw exception for invalid subject value
            if (String.IsNullOrWhiteSpace(subject))
                throw new ArgumentNullException("subject",
                    "subject does not accept a null or empty string as an argument.");

            IEnumerable<CourseDTO> courses = null;

            // Open entity connnection
            using (var context = new CollegeSimEntities())
            {
                try
                {
                    // Mapping retrieved dbset to targeted return type
                    courses = Mapper.Map<IEnumerable<CourseDTO>>(context.Courses.Where(c => c.Subject == subject));
                }
                catch (AutoMapperMappingException ex)
                {
                    // Check for database connection exception
                    if (ex.InnerException.InnerException is SqlException)
                    {
                        throw new InvalidOperationException(
                            "A database connection issue has occurred. Check your network connection and try again, or contact the application developer.");
                    }
                }
            }

            return courses;
        }

        /// <summary>
        /// Returns all Courses with credits hours matching the given amount.
        /// </summary>
        /// <param name="hours">Number of credits hours.</param>
        /// <returns>IEnumerable of CourseDto.</returns>
        public IEnumerable<CourseDTO> FindByCreditHours(int hours)
        {
            // Throw exception for invalid hours value
            if (hours < 1 || hours > 6)
                throw new ArgumentOutOfRangeException("hours",
                    "hours does not accept an integer greater than 6 or less than one as an argument.");

            IEnumerable<CourseDTO> courses = null;

            // Open entity connnection
            using (var context = new CollegeSimEntities())
            {
                try
                {
                    // Mapping retrieved dbset to targeted return type
                    courses = Mapper.Map<IEnumerable<CourseDTO>>(context.Courses.Where(c => c.CreditHours == hours));
                }
                catch (AutoMapperMappingException ex)
                {
                    // Check for database connection exception
                    if (ex.InnerException.InnerException is SqlException)
                    {
                        throw new InvalidOperationException(
                            "A database connection issue has occurred. Check your network connection and try again, or contact the application developer.");
                    }
                }
            }

            return courses;
        }

        /// <summary>
        /// Returns all Courses with the given style, online/hybrid.
        /// </summary>
        /// <param name="online">Online course.</param>
        /// <param name="hybrid">Hybrid course.</param>
        /// <returns>IEnumerable of CourseDto.</returns>
        public IEnumerable<CourseDTO> FindByStyle(bool online, bool hybrid)
        {
            IEnumerable<CourseDTO> courses = null;

            // Open entity connnection
            using (var context = new CollegeSimEntities())
            {
                try
                {
                    // Mapping retrieved dbset to targeted return type
                    courses =
                        Mapper.Map<IEnumerable<CourseDTO>>(
                            context.Courses.Where(c => c.Online == online && c.Hybrid == hybrid));
                }
                catch (AutoMapperMappingException ex)
                {
                    // Check for database connection exception
                    if (ex.InnerException.InnerException is SqlException)
                    {
                        throw new InvalidOperationException(
                            "A database connection issue has occurred. Check your network connection and try again, or contact the application developer.");
                    }
                }
            }

            return courses;
        }

        /// <summary>
        /// Returns a course with the given courseID.
        /// </summary>
        /// <param name="courseID">Interger course courseID</param>
        /// <returns>CourseDto</returns>
        public CourseDTO FindByID(int courseID)
        {
            // Throws exception for invalid courseID value
            if (courseID < 1)
                throw new ArgumentOutOfRangeException("courseID",
                    "courseID does not accept an integer value less than one as an argument.");

            CourseDTO course = null;

            // Open entity connnection
            using (var context = new CollegeSimEntities())
            {
                try
                {
                    // Mapping retrieved entity to targeted return type
                    course = Mapper.Map<CourseDTO>(context.Courses.SingleOrDefault(c => c.CourseID == courseID));

                    // Throws exception if no matching course is found
                    if (course == null)
                        throw new InvalidOperationException("No course with a matching courseID was found.");
                }
                catch (AutoMapperMappingException ex)
                {
                    // Check for database connection exception
                    if (ex.InnerException.InnerException is SqlException)
                    {
                        throw new InvalidOperationException(
                            "A database connection issue has occurred. Check your network connection and try again, or contact the application developer.");
                    }
                }
            }

            return course;
        }

        /// <summary>
        /// Returns a Course with the given title.
        /// </summary>
        /// <param name="title">Course title.</param>
        /// <returns>CourseDto.</returns>
        public CourseDTO FindByTitle(string title)
        {
            // Throw exception for invalid title value
            if (String.IsNullOrWhiteSpace(title))
                throw new ArgumentNullException("title",
                    "title does not accept a null or empty string as an argument.");

            CourseDTO course = null;

            // Open entity connection
            using (var context = new CollegeSimEntities())
            {
                try
                {
                    // Mapping retrieved entity to targeted return type
                    course = Mapper.Map<CourseDTO>(context.Courses.Single(c => c.Title == title));
                }
                catch (AutoMapperMappingException ex)
                {
                    // Check for database connection exception
                    if (ex.InnerException.InnerException is SqlException)
                    {
                        throw new InvalidOperationException(
                            "A database connection issue has occurred. Check your network connection and try again, or contact the application developer.");
                    }
                }
            }

            return course;
        }

        /// <summary>
        /// Saves a new Course.
        /// </summary>
        /// <param name="newCourse">new Course to save.</param>
        public void Insert(CourseDTO newCourse)
        {
            // Throw exception for null dto
            if (newCourse == null)
                throw new ArgumentNullException("newCourse",
                    "newCourse does not accept a null dto as an argument.");

            // Open entity connection
            using (var context = new CollegeSimEntities())
            {
                try
                {
                    // Throws an exception if a course with the given subject/coursenumber already exists
                    if (context.Courses
                        .SingleOrDefault(c => c.Subject == newCourse.Subject && c.Number == newCourse.Number) != null)
                        throw new InvalidOperationException(
                            "A course with the given subject and course number already exists.");

                    // Throws an exception if a course with the given title already exists
                    if (context.Courses.SingleOrDefault(c => c.Title == newCourse.Title) != null)
                        throw new InvalidOperationException("A course with the given title already exists.");

                    context.Courses.Add(Mapper.Map<Course>(newCourse));
                    context.SaveChanges();
                }
                catch (AutoMapperMappingException ex)
                {
                    // Check for database connection exception
                    if (ex.InnerException.InnerException is SqlException)
                    {
                        throw new InvalidOperationException(
                            "A database connection issue has occurred. Check your network connection and try again, or contact the application developer.");
                    }
                }
            }
        }

        /// <summary>
        /// Updates an existing Course.
        /// </summary>
        /// <param name="courseUpdate">Course to update.</param>
        public void Update(CourseDTO courseUpdate)
        {
            // Throw exception for null dto
            if (courseUpdate == null)
                throw new ArgumentNullException("courseUpdate",
                    "courseUpdate does not accept a null dto as an argument.");

            // Open entity connection
            using (var context = new CollegeSimEntities())
            {
                try
                {
                    // Look for cuorse to be updated
                    var course = context.Courses.SingleOrDefault(c => c.CourseID == courseUpdate.CourseID);

                    // Throws an exception if no matching entry is found
                    if (course == null)
                        throw new InvalidOperationException("No entry matching the given course was found.");

                    // Throws an exception if a course with the given subject/coursenumber already exists
                    if (
                        context.Courses.SingleOrDefault(
                            c => c.Subject == courseUpdate.Subject && c.Number == courseUpdate.Number) != null &&
                        courseUpdate.Subject + courseUpdate.Number != course.Subject + course.Number)
                        throw new InvalidOperationException(
                            "Another course with the given subject and course number already exists.");

                    // Throws an exception if a course with the given title already exists
                    if (context.Courses.SingleOrDefault(c => c.Title == courseUpdate.Title) != null &&
                        courseUpdate.Title != course.Title)
                        throw new InvalidOperationException("Another course with the given title already exists.");

                    // Update existing category
                    course.Subject = courseUpdate.Subject;
                    course.Number = courseUpdate.Number;
                    course.Title = courseUpdate.Title;
                    course.CreditHours = courseUpdate.CreditHours;
                    course.ContactHours = courseUpdate.ContactHours;
                    course.Online = courseUpdate.Online;
                    course.Hybrid = courseUpdate.Hybrid;

                    context.SaveChanges();
                }
                catch (AutoMapperMappingException ex)
                {
                    // Check for database connection exception
                    if (ex.InnerException.InnerException is SqlException)
                    {
                        throw new InvalidOperationException(
                            "A database connection issue has occurred. Check your network connection and try again, or contact the application developer.");
                    }
                }
            }
        }

        /// <summary>
        /// Deletes an existing Course.
        /// </summary>
        /// <param name="courseDel">Course to delete.</param>
        public void Delete(CourseDTO courseDel)
        {
            // Throw exception for null dto
            if (courseDel == null)
                throw new ArgumentNullException("courseDel",
                    "courseDel does not accept a null dto as an argument.");

            // Open entity connection
            using (var context = new CollegeSimEntities())
            {
                try
                {
                    // Look for course to be deleted
                    var course = context.Courses.SingleOrDefault(c => c.CourseID == courseDel.CourseID);

                    // Throws an exception if no matching entry is found
                    if (course == null)
                        throw new InvalidOperationException("No entry matching the given course was found.");

                    context.Courses.Remove(course);
                    context.SaveChanges();
                }
                catch (AutoMapperMappingException ex)
                {
                    // Check for database connection exception
                    if (ex.InnerException.InnerException is SqlException)
                    {
                        throw new InvalidOperationException(
                            "A database connection issue has occurred. Check your network connection and try again, or contact the application developer.");
                    }
                }
            }
        }

        #endregion
    }
}
